iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
自我挑戰組

React Native 奇幻之旅系列 第 27

【DAY27】React Native - 應用上架前的準備工作

  • 分享至 

  • xImage
  •  

Android & iOS 上架前都需要對應用進行一些基本設置,比如:應用的package (Bundle ID)、版本、icon...等,這邊簡單分享一下雙系統是如何去設置這些資訊的。

應用版本和包名

Android

versionCode, versionName 都在 android/app/build.gradle 中設置

  • 要上傳到 Play Console 不能有重複的 versionCode,每一次上傳新版本記得都要將 versionCode + 1
android {
    ...
    defaultConfig {
        applicationId "com.test.pokedex"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 3
        versionName "1.0.2"
    }
    ...
}

package 在 android/app/src/main/AndroidManifest.xml 中設置,記得 AndroidManifest.xml 裡面的 package 和 android/app/build.gradle 中的 applicationId 要保持一致,不然打包的時候會失敗。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.test.pokedex">
	...
</manifest>

iOS

iOS 應用的版本和 bundle id 在 General - Identity 設置

  • bundle id 之後講上架 apple store 時會再提到

應用 icon

https://developer.android.com/training/multiscreen/screendensities?hl=zh-tw

上架前有一個非常重要的步驟是替換應用的 icon,如果不替換 icon 的話你的應用下載到設備中看起來就會是這樣的:

Android iOS

Android

應用的 icon, roundIcon 可以在 AndroidManifest.xml 看到,放在 mipmap 中

<application
  android:name=".MainApplication"
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher"
  android:roundIcon="@mipmap/ic_launcher_round"
  android:allowBackup="false"
  android:theme="@style/AppTheme">
  ...
</application>

android/app/src/main/res 底下有多個 mipmap 資料夾,每個資料夾中都有 ic_launcher.png, ic_launcher_round.png

  • mipmap-hdpi
  • mipmap-mdpi
  • mipmap-xhdpi
  • mipmap-xxhdpi
  • mipmap-xxxhdpi

這幾個資料夾是因為每台設備的像素密度不同所以需要製作不同 dpi 的圖片

可以使用工具快速製作不同像素密度的 icon

Icon Kitchen

將下載下來的檔案(android/res)全部覆蓋到 android/app/src/main/res

因為這個工具沒有生成 roundIcon,所以記得將 android/app/src/main/AndroidManifest.xml 中的 android:roundIcon="@mipmap/ic_launcher_round" 刪除。

iOS

Info - Information Property List 這邊添加 Icon Name 為 AppIcon

接著需要上傳應用所需要的 Icon 檔案,在左側找到 Images 然後將原本的 AppIcon 先右鍵刪除:

點擊左下角的 + -> import -> 將剛剛下載的 ios 資料夾整個導入進去:

iOS 如果沒有設置 AppIcon 的話 archive 時會失敗。

Splash screen

除了 App Icon 之外還有一個很重要的是 Splash screen,即應用開啟時的加載畫面。

要修改 Splash Screen 需要安裝 react-native-splash-screen

npm i react-native-splash-screen --save

為了在 App 啟動後關閉 Splash Screen 需要在 App.tsx 中調用 SplashScreen.hide()

import SplashScreen from 'react-native-splash-screen'

const App = () => {
  useEffect(() => {
    const ac = new AbortController()

    setTimeout(() => {
      SplashScreen.hide()
    }, 3000)

    return () => ac.abort()
  }, [])
	
  ..
}

export default App

Android

將要用作 Splash Screen 的圖片分別丟進對應的 dpi 資料夾中並改名為 launch_screen

android/app/src/main/res/drawable 新增 background_splash.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@color/splashscreen_bg"/>
    <item
        android:width="300dp"
        android:height="300dp"
        android:drawable="@mipmap/launch_screen"
        android:gravity="center" />
</layer-list>

android/app/src/main/res/values 新增 colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="splashscreen_bg">#FFFFF8</color>
	<color name="app_bg">#f2f2f2</color>
</resources>

android/app/src/main/res/values/styles.xml 添加

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
        <!-- Add the following line to set the default status bar color for all the app. -->
        <item name="android:statusBarColor">@color/app_bg</item>
        <!-- Add the following line to set the default status bar text color for all the app 
        to be a light color (false) or a dark color (true) -->
        <item name="android:windowLightStatusBar">false</item>
        <!-- Add the following line to set the default background color for all the app. -->
        <item name="android:windowBackground">@color/app_bg</item>
    </style>

    <!-- Adds the splash screen definition -->
    <style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@drawable/background_splash</item>
    </style>
</resources>

修改 android/app/src/main/AndroidManifest.xml

  • 將 .MainApplication 的 android:theme 改為 @style/SplashTheme
  • 在 .MainActivity 添加 android:exported="true"
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.test.pokedex">

    ...

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:allowBackup="false"
      android:theme="@style/SplashTheme">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
    </application>
</manifest>

android/app/src/main/java/<PROJECT_NAME> 新增 SplashActivity.java

package com.test.pokedex; // Change this to your package name.

import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class SplashActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}

MainActivity.java 中新增

...
import android.os.Bundle; // 1.
import org.devio.rn.splashscreen.SplashScreen; // 2.

public class MainActivity extends ReactActivity {
  ...
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        SplashScreen.show(this); // 3.
        super.onCreate(savedInstanceState);
    }
}

app/src/main/res/layout(如果沒有這個資料夾就新增) 中新增 launch_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@mipmap/launch_screen" android:scaleType="centerCrop" />
</RelativeLayout>

iOS

使用工具生成 image sets,勾選 4x iOS

App Icon Generator

Xcode 左側找到 Images - + Image Set 新增 SlashIcon,將剛剛下載下來的三張圖片拖移進去

左側找到 LaunchScreen - View Controller Scene - View Controller - View 將原本畫面上的文字刪掉

右上角找到「+」新增 image view

右側 Image 選則剛剛新增的 SplashIcon

如果是手動將元素移到畫面中央的話,在不同解析度的設備上面會跑版,所以需要在右下角找到 Align 圖示新增 Constraints。

水平垂直都設為 0 的話元素會在畫面正中央:

Constraints 都設置好之後可以切換不同設備測試一下是否正常:

General - App Icons and Launch Screen 中將 Launch Screen File 設為 LaunchScreen.storyboard

接著在 ios/<PROJECT_NAME>/AppDelegate.mm 新增

...
#import "RNSplashScreen.h" // Add this

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ...

  [super application:application didFinishLaunchingWithOptions:launchOptions];
  [RNSplashScreen show];

  return YES;
}
...

Expo

https://docs.expo.dev/versions/latest/sdk/splash-screen/

app.json 中設置

{
  "expo": {
    ...
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    ...
}

或者可以使用 expo-splash-screen

npx expo install expo-splash-screen
import React, { useCallback, useEffect, useState } from 'react';
import { Text, View } from 'react-native';
import Entypo from '@expo/vector-icons/Entypo';
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';

// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();

export default function App() {
  const [appIsReady, setAppIsReady] = useState(false);

  useEffect(() => {
    async function prepare() {
      try {
        // Pre-load fonts, make any API calls you need to do here
        await Font.loadAsync(Entypo.font);
        // Artificially delay for two seconds to simulate a slow loading
        // experience. Please remove this if you copy and paste the code!
        await new Promise(resolve => setTimeout(resolve, 2000));
      } catch (e) {
        console.warn(e);
      } finally {
        // Tell the application to render
        setAppIsReady(true);
      }
    }

    prepare();
  }, []);

  const onLayoutRootView = useCallback(async () => {
    if (appIsReady) {
      // This tells the splash screen to hide immediately! If we call this after
      // `setAppIsReady`, then we may see a blank screen while the app is
      // loading its initial state and rendering its first pixels. So instead,
      // we hide the splash screen once we know the root view has already
      // performed layout.
      await SplashScreen.hideAsync();
    }
  }, [appIsReady]);

  if (!appIsReady) {
    return null;
  }

  return (
    <View
      style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}
      onLayout={onLayoutRootView}>
      <Text>SplashScreen Demo! 👋</Text>
      <Entypo name="rocket" size={30} />
    </View>
  );
}

Android 12 重複的 Splash Screen

如果是按照本篇前面的方式設定就不會發生這種情況。

在上架時收到了 Android 的相容性警告,說是使用 Android 12 以上版本會在啟動程式時出現兩個 Splash Screen:

這是因為從 Android 12 開始應用冷啟動和熱啟動時會顯示預設的 Splash Screen。預設的 Splash Screen 會是 launcher icon 和 theme 的 windowBackground 組合的。

如果應用的 Splash Screen 是自定義的,在 Android 12 或更高版本的設備上啟動應用程式就會出現重複的Splash Screen,首先顯示系統預設 Splash Screen,然後才顯示自定義的。

如果需要顯示自定義的 Splash Screen,那就需要將預設的覆蓋掉,覆蓋方式如下:

android/app/src/main/res/values/styles.xml 中添加 SplashTheme

  • "android:windowIsTranslucent" true
  • "android:windowBackground" @drawable/background_splash
  • background_splash 是自定義的 splash screen 畫面
<resources>
    ...

    <style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@drawable/background_splash</item>
    </style>
</resources>

打開 android/app/src/main/AndroidManifest.xml,修改 MainApplication 的 android:theme@style/SplashTheme

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.test.pokedex">

    ...

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:allowBackup="false"
      android:theme="@style/SplashTheme">
      ...
    </application>
</manifest>

注意:如果不需要自定義 Splash Screen,也可以只修改 windowSplashScreenAnimatedIconwindowSplashScreenBackground,更多請參考 Migrate your splash screen implementation to Android 12 and later

參考資料


上一篇
【DAY26】EXPO + Github Action 自動化構建 React Native 應用
下一篇
【DAY28】打包 Android APP 發布到 Play Store 測試與上架
系列文
React Native 奇幻之旅31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言